<?php
/**
 * 校验器
 *
 * @package if
 * @subpackage module.validate
 * @since 1.0
 */
class IValidator extends IObject {
	private $errors = array();
	private $name;
	protected $value;
	private $real;
	
	/**
	 * 构造校验器
	 *
	 * @param string $name 被校验的变量名
	 * @param mixed $value 被校验的值
	 * @param boolean $opts 选项，目前只有trim这个选项
	 * @since 1.0
	 */
	function __construct($name, &$value, $opts = array()) {
		$this->name = $name;
		$this->real = $value;
		$this->value = &$value;
		if ((!isset($opts["trim"]) || $opts["trim"]) && is_string($this->value)) {
			$this->value = trim($this->value);
		}
	}
	
	/**
	 * 应用规则到指定的值
	 * 
	 * @param string $rule 规则名
	 * @param mixed $params 参数
	 * @param string|null $message 验证失败时的提示消息
	 * @return boolean
	 * @since 1.0
	 */
	function apply($rule, $params = null, $message = null) {
		$callback = null;
		if (is_array($rule) && isset($rule[0]) && isset($rule[1])) {
			$callback = array($rule[0], $rule[1]);
		}
		else if (is_string($rule)) {
			$callback = array($this, $rule);
		}
		if (!method_exists($callback[0], $callback[1])) {
			throw new IException("undefined validate rule '" . $callback[1] .  "'", 0x100030);
		}
		else {
			if (is_array($params) && isset($params["param"]) && count($params) == 1) {//v1.1.0
				$params = $params["param"];
			}
			$bool = call_user_func_array($callback, array($params));
			if (!$bool) {
				if (is_null($message)) {//默认信息
					$message = x("~@validator.{$rule}");
					if (!$message) {
						$message = "failed to check '" . $callback[1] . "' for {$this->name}";
					}
				}
				else if (preg_match("/^%\{(.+)\}$/", $message, $match)) {//本地化
					$message = l($match[1]);
				}
				//替换变量
				if (strstr($message, "%{")) {
					if (!is_array($params)) {
						$params = array($params);
					}
					foreach ($params as $key=>$value) {
						$message = str_replace("%{{$key}}", $value, $message);
					}
				}
				$this->errors[] = $message;
				
				return false;
			}
		}
		return true;
	}
	
	function applyOpts(array $opts) {
		if (!empty($opts)) {
			$rule = $opts[0];
			$params = isset($opts[1])?$opts[1]:null;
			$message = isset($opts[2])?$opts[2]:null;
			return $this->apply($rule, $params, $message);
		}
		return true;
	}
	
	/**
	 * 所有验证发生的错误
	 *
	 * @return array
	 * @since 1.0
	 */
	function errors() {
		return $this->errors;
	}
	
	/**
	 * 检查值的长度
	 * 
	 * 支持的参数：
	 * - min 最小长度
	 * - max 最大长度
	 *
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function length($params = null) {
		if (is_array($params)) {
			$length = is_array($this->value) ? count($this->value) : strlen($this->value);
			if (isset($params["min"]) && $params["min"] > $length) {
				return false;
			}
			if (isset($params["max"]) && $params["max"] < $length) {
				return false;
			}
		}
		return true;
	}
	
	/**
	 * 检查是否为空
	 * 
	 * 支持的参数：
	 * - strict <boolean> 是否严格，默认为false，如果设置为true，那么字符串"0"将视为非空
	 *
	 * @param array $params 参数
	 * @return boolean
	 */
	function required($params = null) {
		if (isset($params["strict"]) && $params["strict"] && $this->value === "0") {
			return true;
		}
		return !empty($this->value);
	}
	
	/**
	 * 检查是否不为null
	 * 
	 * 支持的参数：
	 * - 无
	 *
	 * @param unknown_type $params
	 * @return unknown
	 */
	function notNull($params = null) {
		return !is_null($this->real);
	}
	
	/**
	 * 检查是否不为空
	 * 
	 * 同required唯一的区别是，把只有一个字符的字符串"0"也当作空
	 * 
	 * 支持的参数：
	 * - 无
	 *
	 * @param unknown_type $params
	 * @return unknown
	 * @see required
	 */
	function notEmpty($params = null) {
		return !empty($this->value);
	}

	/**
	 * 检查是否是字母的组合
	 * 
	 * 支持的参数：
	 * - 无
	 *
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function alpha($params = null) {
		return preg_match("/^[a-z]+$/i", $this->value);
	}
	
	/**
	 * 检验是否为单词(只含数字、字母、下划线)
	 * 
	 * 支持的参数：
	 * - 无
	 *
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function word($params = null) {
		return preg_match("/^\w+$/", $this->value);
	}
	
	/**
	 * 检查是否为字母和数字的组合
	 * 
	 * 支持的参数：
	 * - 无
	 *
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function alphaNum($params = null) {
		return preg_match("/^[\da-z]+$/i", $this->value);
	}
	
	/**
	 * 检查一个值是否在两个值之间
	 * 
	 * 支持的参数：
	 * - min 最小值
	 * - max 最大值
	 *
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function between($params = null) {
		if (is_array($params)) {
			if (isset($params["min"]) && $this->value < $params["min"]) {
				return false;
			}
			if (isset($params["max"]) && $this->value > $params["max"]) {
				return false;
			}
		}
		return true;
	}
	
	/**
	 * 检查是否为空字符串或者只有空白字符
	 * 
	 * 支持的参数：
	 * - 无
	 *
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function blank($params = null) {
		return !preg_match("/[^\s]/", $this->value);
	}
	
	/**
	 * 检查是否大于比较的值
	 * 
	 * 支持的参数：
	 * - scalar 比较的值
	 *
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function greater($params = null) {
		return (is_scalar($params) && $this->value > $params);
	}
	
	/**
	 * 检查是否小于比较的值
	 * 
	 * 支持的参数：
	 * - scalar 比较的值
	 *
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function less($params = null) {
		return (is_scalar($params) && $this->value < $params);
	}
	
	/**
	 * 检查是否大于或等于比较的值
	 * 
	 * 支持的参数：
	 * - scalar 比较的值
	 *
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function greaterEqual($params = null) {
		return (is_scalar($params) && $this->value >= $params);
	}
	
	/**
	 * 检查是否小于等于比较的值
	 * 
	 * 支持的参数：
	 * - scalar 比较的值
	 *
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function lessEqual($params = null) {
		return (is_scalar($params) && $this->value <= $params);
	}
	
	/**
	 * 检查是否相等
	 * 
	 * 支持的参数：
	 * - scalar 比较的值
	 *
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function equal($params = null) {
		return ($this->value == $params);
	}
	
	/**
	 * 检查是否和比较的值不相等
	 *
	 * 支持的参数：
	 * - scalar 比较的值
	 * 
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function notEqual($params = null) {
		return ($this->value != $params);
	}
	
	/**
	 * 检查是否匹配正则表达式
	 *
	 * 支持的参数：
	 * - string|array 正则表达式列表
	 * 
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function regex($params = null) {
		if (!is_array($params)) {
			$params = array($params);
		}
		foreach ($params as $param) {
			if (is_string($param) && !@preg_match($param, $this->value)) {
				return false;
			}
		}
		return true;
	}
	
	/**
	 * 检查日期格式
	 * 
	 * 此处参考了CakePHP的实现 http://api.cakephp.org/validation_8php-source.html
	 *
	 * 支持的参数：
	 * - string dmy|mdy|ymd|dMy|Mdy|My|my
	 * 
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function date($params = null) {
		$regex = array();
		if (is_null($params)) {
			$params = "ymd";
		}
		$regex["dmy"] = "%^(?:(?:31(\\/|-|\\.|\\x20)(?:0?[13578]|1[02]))\\1|(?:(?:29|30)(\\/|-|\\.|\\x20)(?:0?[1,3-9]|1[0-2])\\2))(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$|^(?:29(\\/|-|\\.|\\x20)0?2\\3(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\\d|2[0-8])(\\/|-|\\.|\\x20)(?:(?:0?[1-9])|(?:1[0-2]))\\4(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$%";
         $regex["mdy"] = "%^(?:(?:(?:0?[13578]|1[02])(\\/|-|\\.|\\x20)31)\\1|(?:(?:0?[13-9]|1[0-2])(\\/|-|\\.|\\x20)(?:29|30)\\2))(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$|^(?:0?2(\\/|-|\\.|\\x20)29\\3(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:(?:0?[1-9])|(?:1[0-2]))(\\/|-|\\.|\\x20)(?:0?[1-9]|1\\d|2[0-8])\\4(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$%";
         $regex["ymd"] = "%^(?:(?:(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)))(\\/|-|\\.|\\x20)(?:0?2\\1(?:29)))|(?:(?:(?:1[6-9]|[2-9]\\d)?\\d{2})(\\/|-|\\.|\\x20)(?:(?:(?:0?[13578]|1[02])\\2(?:31))|(?:(?:0?[1,3-9]|1[0-2])\\2(29|30))|(?:(?:0?[1-9])|(?:1[0-2]))\\2(?:0?[1-9]|1\\d|2[0-8]))))$%";
         $regex["dMy"] = "/^((31(?!\\ (Feb(ruary)?|Apr(il)?|June?|(Sep(?=\\b|t)t?|Nov)(ember)?)))|((30|29)(?!\\ Feb(ruary)?))|(29(?=\\ Feb(ruary)?\\ (((1[6-9]|[2-9]\\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00)))))|(0?[1-9])|1\\d|2[0-8])\\ (Jan(uary)?|Feb(ruary)?|Ma(r(ch)?|y)|Apr(il)?|Ju((ly?)|(ne?))|Aug(ust)?|Oct(ober)?|(Sep(?=\\b|t)t?|Nov|Dec)(ember)?)\\ ((1[6-9]|[2-9]\\d)\\d{2})$/";
         $regex["Mdy"] = "/^(?:(((Jan(uary)?|Ma(r(ch)?|y)|Jul(y)?|Aug(ust)?|Oct(ober)?|Dec(ember)?)\\ 31)|((Jan(uary)?|Ma(r(ch)?|y)|Apr(il)?|Ju((ly?)|(ne?))|Aug(ust)?|Oct(ober)?|(Sept|Nov|Dec)(ember)?)\\ (0?[1-9]|([12]\\d)|30))|(Feb(ruary)?\\ (0?[1-9]|1\\d|2[0-8]|(29(?=,?\\ ((1[6-9]|[2-9]\\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00)))))))\\,?\\ ((1[6-9]|[2-9]\\d)\\d{2}))$/";
         $regex["My"] = "%^(Jan(uary)?|Feb(ruary)?|Ma(r(ch)?|y)|Apr(il)?|Ju((ly?)|(ne?))|Aug(ust)?|Oct(ober)?|(Sep(?=\\b|t)t?|Nov|Dec)(ember)?)[ /]((1[6-9]|[2-9]\\d)\\d{2})$%";
         $regex["my"] = "%^(((0[1-9]|10|11|12)([- /.])(([1][9][0-9][0-9])|([2][0-9][0-9][0-9]))))$%";
         return (isset($regex[$params]) && preg_match($regex[$params], $this->value));
	}
	
	/**
	 * 判断是否为时间格式
	 * 
	 * 支持的参数：
	 * - 无
	 * 
	 * (HH:MM) or am/pm ([H]H:MM[a|p]m)
	 *
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function time($params = null) {
		$regex = "%^((0?[1-9]|1[012])(:[0-5]\d){0,2}([AP]M|[ap]m))$|^([01]\d|2[0-3])(:[0-5]\d){0,2}$%";
		return preg_match($regex, $this->value);
	}
	
	/**
	 * 检查是否为bool型的数据
	 * 
	 * 支持的参数：
	 * - strict 是否严格比较
	 *
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function bool($params = null) {
		$values = array();
		if (is_array($params) && isset($params["strict"]) && $params["strict"]) {
			return is_bool($this->real);
		}
		else {
			$values = array(0, 1, "0", "1", true, false);
			if (!in_array($this->value, $values, true)) {
				return false;
			}
		}
		return true;
	}
	
	/**
	 * 检查是否符合Email规则
	 * 
	 * 支持的参数：
	 * - 无
	 *
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function email($params = null) {
		$regex = "/^[a-z0-9]+([\._\-\+]*[a-z0-9]+)*@([a-z0-9]+[\-a-z0-9]*[a-z0-9]+\.)+[a-z0-9]+$/i";
		return preg_match($regex, $this->value);
	}
	
	/**
	 * 检查扩展名是否在扩展名列表中
	 *
	 * 支持的参数：
	 * - string|array 扩展名列表
	 * 
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function ext($params = null) {
		$ext = pathinfo($this->value, PATHINFO_EXTENSION);
		if (is_string($params)) {
			$params = explode(",", $params);
		}
		return (is_array($params) && (in_array($ext, $params) || in_array("." . $ext, $params)));
	}
	
	/**
	 * 检查是否为IP字符串
	 * 
	 * 支持的参数：
	 * - 无
	 *
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function ip($params = null) {
		$long = @ip2long($this->value);
		return ($long != -1 && $long !== false);
	}
	
	/**
	 * 检查值得最小长度
	 * 
	 * 支持的参数：
	 * - integer 长度
	 *
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function minLength($params = null) {
		return $this->length(array("min" => $params));
	}
	
	/**
	 * 检查值的最大长度
	 * 
	 * 支持的参数：
	 * - integer 长度
	 *
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function maxLength($params = null) {
		return $this->length(array("max" => $params));
	}
	
	/**
	 * 检查是否为数字
	 * 
	 * 支持的参数：
	 * - 无
	 *
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function numeric($params = null) {
		return is_numeric($this->value);
	}
	
	/**
	 * 检查值是否在一定范围内
	 * 
	 * 支持的参数：
	 * - integer low 最低值
	 * - integer high 最高值
	 * - integer step 步长
	 * - boolean strict 是否严格比较
	 *
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function range($params = null) {
		if (is_array($params)) {
			$low = isset($params["low"])?$params["low"]:0;
			$high = isset($params["high"])?$params["high"]:0;
			$step = isset($params["step"])?$params["step"]:1;
			$strict = isset($params["strict"])?$params["strict"]:false;
			$range = range($low, $high, $step);
			if (!in_array($this->value, $range, $strict)) {
				return false;
			}
		}
		return true;
	}
	
	/**
	 * 检查值是否在列表中
	 * 
	 * 支持的参数：
	 * - array 值列表
	 *
	 * @param mixed $params 参数
	 * @return boolean
	 * @since 1.0
	 */
	function in($params = null) {
		return (is_array($params) && in_array($this->value, $params));
	}
	
	/**
	 * 检查是否为URL
	 * 
	 * 支持的参数：
	 * - 无
	 *
	 * @param null $params
	 */
	function url($params = null) {
		return preg_match("/^(?:(?:http|https):\\/\\/)?(?:(?:(?:25[0-5]|2[0-4]\d|(?:(?:1\d)?|[1-9]?)\d)\.){3}(?:25[0-5]|2[0-4]\d|(?:(?:1\d)?|[1-9]?)\d)"
							. "|(?:[0-9a-z]{1}[0-9a-z\\-]*\\.)*(?:[0-9a-z]{1}[0-9a-z\\-]{0,62})\\.(?:[a-z]{2,6}|[a-z]{2}\\.[a-z]{2,6})"
							. "(?::[0-9]{1,4})?)(?:\\/?|\\/[\\w\\-\\.,\'@?^=%&:;\/~\\+#]*[\\w\\-\\@?^=%&\/~\\+#])$/i", $this->value);
	}
	
	/**
	 * 检查表达式是否成立
	 * 
	 * 支持的参数： 
	 * - string 表达式
	 *
	 * - strstr(self,'4kiki')>0
	 * - strlen(self)==12
	 * 
	 * @param string $params 表达式，使用self 表示值本身
	 * @return boolean
	 * @since 1.0
	 */
	function expr($params = null) {
		if (is_string($params)) {
			$ret = null;
			$params = preg_replace("/\bself\b/s", "\$this->value", $params);
			$this->_log("IValidate appy expr '{$params}'");
			@eval("\$ret=({$params});");
			return $ret;
		}
		return false;
	}
	
	/**
	 * 检查是否为邮政编码（中国大陆）
	 * 
	 * 支持的参数：
	 * - 无
	 *
	 * @param null $params
	 * @return boolean
	 * @since 1.0
	 */
	function postcode($params = null) {
		return preg_match("/^[1-9]\d{5}$/", $this->value);
	}
	
	/**
	 * 检查是否为QQ号（中国大陆）
	 * 
	 * 支持的参数：
	 * - 无
	 *
	 * @param null $params
	 * @return boolean
	 * @since 1.0
	 */
	function qq($params = null) {
		return preg_match("/^[1-9]\d{4,12}$/", $this->value);
	}
	
	/**
	 * 检查是否为手机号（中国大陆）
	 *
	 * 支持的参数：
	 * - 无
	 * 
	 * @param null $params
	 * @return boolean
	 * @since 1.0
	 */
	function mobile($params = null) {
		return preg_match("/^((\(\d{2,3}\))|(\d{3}\-))?\s*(13|15|18)\d{9}$/", $this->value);
	}
	
	/**
	 * 是否有验证错误
	 *
	 * @return boolean
	 * @since 1.0
	 */
	function hasErrors() {
		return !empty($this->errors);
	}
	
	/**
	 * 检查是否为整数
	 *
	 * @param null $params
	 * @return boolean
	 * @since 1.1.0
	 */
	function digit($params = null) {
		return preg_match("/^\d+$/", $this->value);
	}
}


?>